package miniMax;

import domain.Agent;
import domain.AgentDumbAuto;
import domain.AgentGreedy;
import domain.AgentHuman;
import domain.AgentSmart;
import domain.Board;
import domain.Debug;
import domain.Flag;
import domain.Game.action;
import domain.Square;
import domain.exceptions.CannotMoveThereException;
//import domain.exceptions.NoSuchSquareException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Vector;

/**
 *
 * @author Shai Givony
 */

public class StateNode implements Comparable<Object> {
	private Board				_board;
    //private Agent				_agentTurn;
    //private Agent				_agentNotTurn;
    private Vector<Agent>       _agents;
    //private Vector<Flag>        _flags;
    private LinkedList<action>  _path;
	private Agent 				_agentGood;
	private Agent 				_agentBad;

	//Constructor
	public StateNode(Board board/*, Agent agentTurn, Agent agentNotTurn*/, Vector<Agent> agents, Vector<Flag> flags, LinkedList<action> path, Agent agentGood, Agent agentBad) {
		// MiniMaxNode receives all fields by reference, and clone them all.
		/*Debug.println("@"+path.toString()+"@");
		if (path.toString().contains("[RIGHT]")){
			Debug.println("wellcome");
		}*/
		cloneBoard(board);
		this._agentGood = agentGood;
		this._agentBad = agentBad;
		//this._agentTurn			= agentTurn;		// this is being fixed in cloneRestOfAgents();
		//this._agentNotTurn		= agentNotTurn;		// this is being fixed in cloneRestOfAgents();
	    cloneAllAgents(agents);
		//cloneRestOfAgents(agents);
	    //cloneFlags(flags);
	    computeF();
	    clonePath(path);
	}

	// Cloning methods:
	// ------------------------------------------------------------------------
	private void clonePath(LinkedList<action> path) {
		LinkedList<action> clonedPath = new LinkedList<action>();
		for (Iterator<action> iter = path.iterator(); iter.hasNext();) {
			action dir = iter.next();
			clonedPath.addLast(dir);
		}
		this._path = clonedPath;
	}

//    // Copy Constructor
//    public ANode(ANode other) {
//    	// ANode receives all fields by reference, and clone them all.
//    	cloneBoard(other._board);
//    	this._me			= other._me;
//        cloneRestOfAgents(agents);
//        cloneFlags(flags);
//        this._g      		= g;
//        this._path   		= path;
//    }

//    private void cloneFlags(Vector<Flag> flags) {
//		this.set_flags(new Vector<Flag>());
//		for (Iterator iter = flags.iterator(); iter.hasNext();) {
//			Flag flag = (Flag) iter.next();
//			Square localSquare = this._board.getSquare(flag.getSquare().getRow(), flag.getSquare().getColumn());
//
//			Flag localFlag = localSquare.getFlag();
//
//			localFlag.setSquare(localSquare);
//			get_flags().addElement(localFlag);
//		}
//	}

	private void cloneBoard(Board board) {
		this._board = new Board(board);
	}

	private void cloneAllAgents(Vector<Agent> agents) {
    	this._agents	= new Vector<Agent>();
    	for (Iterator<Agent> iter = agents.iterator(); iter.hasNext();) {
			Agent agent = iter.next();
			Agent clonedAgent = null;
			if ( agent instanceof AgentSmart ){
				clonedAgent = new AgentSmart((AgentSmart)agent);
			} else if ( agent instanceof AgentDumbAuto ){
				clonedAgent = new AgentDumbAuto((AgentDumbAuto)agent);
			} else if ( agent instanceof AgentGreedy ){
				clonedAgent = new AgentGreedy((AgentGreedy)agent);
			} else if ( agent instanceof AgentHuman ){
				clonedAgent = new AgentHuman((AgentHuman)agent);
			}
			//TODO: add CC to rest of agents and use it here
			this._agents.addElement(clonedAgent);


			// fixing clonedAgent reference to local copy of board:
			clonedAgent.getSquare().getRow();
			Square localCurrentSquare = _board.getSquare(clonedAgent.getSquare().getRow(),clonedAgent.getSquare().getColumn());
			Square localPreviousSquare = _board.getSquare(clonedAgent.getPreviousSquare().getRow(),clonedAgent.getPreviousSquare().getColumn());
			clonedAgent.setSquare(localCurrentSquare);
			clonedAgent.setPreviousSquare(localPreviousSquare);

			// fixing local copy of board to refer to the cloned agents:
			localCurrentSquare.setAgent(clonedAgent);
			if ( localCurrentSquare != localPreviousSquare ){
				localPreviousSquare.setAgent(null);
			}

		}//end of for

    	// updating fields _agentMax & _agentMin:
    	int i = 0;
    	for (Iterator<Agent> iter = agents.iterator(); iter.hasNext();) {
			Agent agent = iter.next();
			if (agent == this._agentGood){
				this._agentGood = _agents.elementAt(i);
			} else if (agent == this._agentBad){
				this._agentBad = _agents.elementAt(i);
			}
			i++;
		}

    	// updating enemies:
    	this._agentGood.setEnemy(this._agentBad);
    	this._agentBad.setEnemy(this._agentGood);
	}

//	private void cloneRestOfAgents(Vector<Agent> agents) {
//    	this._agents	= new Vector<Agent>();
//    	for (Iterator iter = agents.iterator(); iter.hasNext();) {
//			Agent otherAgent = (Agent) iter.next();
//			Agent clonedAgent = null;
//			if ( otherAgent == this._agentTurn ){
//				clonedAgent = new AgentSmart((AgentSmart)otherAgent);
//				this._agentTurn = (AgentSmart)clonedAgent;
//			} else if ( otherAgent instanceof AgentSmart ){
//				clonedAgent = new AgentSmart((AgentSmart)otherAgent);
//			} else if ( otherAgent instanceof AgentDumbAuto ){
//				clonedAgent = new AgentDumbAuto((AgentDumbAuto)otherAgent);
//			} else if ( otherAgent instanceof AgentGreedy ){
//				clonedAgent = new AgentGreedy((AgentGreedy)otherAgent);
//			} else if ( otherAgent instanceof AgentHuman ){
//				clonedAgent = new AgentHuman((AgentHuman)otherAgent);
//			}
//			//TODO: add CC to rest of agents and use it here
//			this._agents.addElement(clonedAgent);
//			//fixing clonedAgent reference to local copy of board.
//			clonedAgent.getSquare().getRow();
//			Square localSquare = _board.getSquare(clonedAgent.getSquare().getRow(),clonedAgent.getSquare().getColumn());
//			clonedAgent.setSquare(localSquare);
//			localSquare.setAgent(clonedAgent);
//
//		}//end of for
//	}
	// ------------------------------------------------------------------------ (end of cloning methods)

	public boolean isGoalNode() {
		return this.get_flags().isEmpty();
	}

	private void addToPath(action dir){
		this._path.addLast(dir);
	}

	public StateNode simulateNode(Agent agentInTurn, action act, boolean ignoreIce){
        // constructing the actual new node:
		StateNode ans = new StateNode(this._board/*, this._agentTurn, this._agentNotTurn*/,
				this._agents, this.get_flags(), this._path, this._agentGood, this._agentBad);
        ans.addToPath(act);			//adding direction to the direction path

        // simulate <agentInTurn> is performing <act> (act = move U/R/D/L or Shoot)
        try {
        	if (agentInTurn == this._agentGood)
        		ans.simulateAgentActing(ans._agentGood, act, ignoreIce);
        	else if (agentInTurn == this._agentBad)
        		ans.simulateAgentActing(ans._agentBad, act, ignoreIce);
		} catch (CannotMoveThereException e) {
			Debug.println("State node: "+e.getMessage());
		}

//		// simulate Sentry is moving
//        try {
//        	//Agent sentry = getSentryFromAgents();
//        	ans.simulateMoveAgentSentry(act);
//		} catch (CannotMoveThereException e) {
//			System.err.println("Anode: "+e.getMessage());
//		}

        return ans;
    }

//    private Agent getSentryFromAgents() {
//		for (Iterator iter = this._agents.iterator(); iter.hasNext();) {
//			Agent agent = (Agent) iter.next();
//			if ( agent != this._me &&
//					( agent instanceof AgentDumbAuto  ||  agent instanceof AgentHuman )){
//				return agent;
//			}
//		}
//		System.err.println("No Sentry was Found!");
//		return null;
//	}

	private void simulateAgentActing(Agent agent, action act, boolean ignoreIce) throws CannotMoveThereException {
    	//this._me.moveAStar(dir, this._board);
		if (!ignoreIce){
			agent.act(act, this._board, this._agents);
		}
		else{
			agent.move(act, this._board);
		}
    	//this._g = this._me.getScore();	// update g value
		if (agent.isFlagLatelyTaken()){
			////////////////////////////////////HERE/////////////////////////////////////
			//this._g += Game.FLAG_VALUE;
			agent.setFlagLatelyTaken(false);
		}
		//this._h = computeHeuristic();	// update h value
    	//this._f = computeF();			// update f value
	}

//	private void simulateMoveAgentSentry(action dir) throws CannotMoveThereException {
//    	getSentryFromAgents().play(this._board, this._agents);
//    	this._h = computeHeuristic();	// update h value
//    	this._f = computeF();			// update f value
//	}

//	private int computeHeuristic(){
//		int ans = 0;
//
//        return ans;
//    }

    /**
     * the function check if you will die in the current location
     * @return panaltyForDeath if true, else return zero
     */
//    private int isInLineOfFire() {
//		for (Iterator<Agent> iterator = _agents.iterator(); iterator.hasNext();) {
//			Agent smith = (Agent) iterator.next();
//			if(!smith.equals(this._me)){	//avoid calling to myself
//				int myLocationX=_me.getSquare().getColumn();
//				int myLocationY=_me.getSquare().getRow();
//				int smithLocationX=smith.getSquare().getColumn();
//				int smithLocationY=smith.getSquare().getRow();
//				if(myLocationX==smithLocationX&&noWall(myLocationY,smithLocationY, true,myLocationX)||myLocationY==smithLocationY&&noWall(myLocationX,smithLocationX,false,myLocationY)){
//					return paneltyForDeath;
//				}
//			}
//		}
//		return 0;
//	}
//    /**
//     * @param from: the coordinate to start looking from
//     * @param to: the coordinate to look until
//     * @param vetrical: a boolean parameter, if true the check will be performed up-down, else left-right
//     * @param cordinate: a location that the for will run on
//     * @return true if there is no wall between the (from, coordinate)\(coordinate, from) the
//     *		   to (to, coordinate)\(coordinate,to) [depending of vertical]. else retrun false.
//     */
//	private boolean noWall(int from, int to, boolean vertical,int coordinate) {
//		//small adjustment so we will scan from the left to right and up down. to prevent
//		//problem with the following for loops.
//		int min=Math.min(from,to);
//		int max=Math.max(from,to);
//		from=min;
//		to=max;
//
//		if(vertical){
//			for (int i = from; i < to; i++) {
//				if(_board.getSquare(i, coordinate) instanceof SquareWall ){
//					return false;
//				}
//			}
//		}else{//chack if there is a wall from "from" to "to" in horizontaly in the "cordination
//			for (int i = from; i < to; i++) {
//				if(_board.getSquare(coordinate, i) instanceof SquareWall ){
//					return false;
//				}
//			}
//		}
//		return true;
//	}//end of if wall

	private int computeF(){
        return this._agentGood.get_flagsTakenByMeValue() -
		   	   this._agentBad.get_flagsTakenByMeValue();
    }



	// Probably should not be commented and I should implement it:
    public int compareTo(Object o) {
        StateNode other = (StateNode)o;

        if ( this == other ){
            return 0;
        }

        if ( this.getValue() > other.getValue() )
            return 1;
        else if ( this.getValue() < other.getValue() )
            return -1;
        else
            return 0;
    }

    public LinkedList<action> getPath() {
        return _path;
    }

    public Vector<Agent> getAgents() {
        return _agents;
    }


    //TODO: deeper check of equals is needed (for history)
    @Override
    public boolean equals(Object other){
    	if (!(other instanceof StateNode)){
			return false;
		}
    	StateNode otherANode = (StateNode) other;
    	return this._board.equals(otherANode._board) &&
    		   this._agentGood.equals(otherANode._agentGood) &&
    		   this._agentBad.equals(otherANode._agentBad) &&
    		   this._path.equals(otherANode._path) &&
    		   this.get_flags().equals(otherANode.get_flags());
    }

//    /**
//     *
//     * @param agents2: Vector of agents
//     * @param agents: Vector of agents
//     * @return true if the agents are the same in both vector, not necessarily the same order
//     */
//    private boolean sameAgents(Vector<Agent> agents2, Vector<Agent> agents) {
//		return agents.containsAll(agents2)&&agents2.containsAll(agents);
//	}

//	/**
//     * @return: true if the vector has the same flags not necessarily the same order
//     *          based on the isEquals of Flag.
//     */
//	private boolean sameFlags(Vector<Flag> flags2, Vector<Flag> flags) {
//		return flags.containsAll(flags)&&flags2.containsAll(flags);
//	}
//
//	private Board getBoard() {
//		return this._board;
//	}

//	private Object getAgentTurn() {
//		// TODO Auto-generated method stub
//		return _agentTurn;
//	}

	@Override
	public String toString() {
		String ans = "MiniMaxNode: " + this._path ;
		ans += "    " + this._agentGood;
		if (this._agentGood.isDead())
			ans += " Agent Good is Dead";
		ans += "    " + this._agentBad;
		if (this._agentBad.isDead())
			ans += " Agent Bad is Dead";
		return ans;
	}

	public int getValue() {
		//return this._agentMax.get_flagsTakenByMeValue() -
		//	   this._agentMin.get_flagsTakenByMeValue();
		return this._agentGood.getScore() - this._agentBad.getScore();
	}
	
	public int getValue_maxiMax(Agent agentInTurn) {
		return agentInTurn.getScore();
	}

	public Agent getOpponentOf(Agent agent){
		if (agent == this._agentGood){
			return this._agentBad;
		}
		else if (agent == this._agentBad){
			return this._agentGood;
		}
		return null;
	}

	public Agent getAgentGood() {
		return this._agentGood;
	}

	public Agent getAgentBad() {
		return this._agentBad;
	}

	public action getActionThatGotMeHere() {
		if (this._path.isEmpty())
			return null;
		return this._path.getLast();
	}

	public Vector<Flag> get_flags() {
		return this._board.getFlags();
	}

	public double getValueGoodAgent() {
		return this._agentGood.getScore();
	}
	
	public double getValueBadAgent() {
		return this._agentBad.getScore();
	}
}
